package com.sharkpro.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.sharkpro.constants.RedisConstant;
import com.sharkpro.constants.SaltConstant;
import com.sharkpro.security.CustomerAuthenticationException;
import com.sharkpro.security.CustomerUserDetailsService;
import com.sharkpro.security.LoginFailHandler;
import com.sharkpro.utils.JwtUtils;
import com.sharkpro.utils.RedisUtil;
import com.sharkpro.utils.ResultUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

/**
 * OncePerRequestFilter是spring boot 提供的过滤器抽象类
 * 在Spring security应用广泛，可以用来过滤请求
 * 这个过滤器通常被 用于继承实现并在每次请求时执行
 */
//@Component
public class VerifyFilter extends OncePerRequestFilter {
    @Resource
    private RedisUtil redisUtils;
    @Resource
    private CustomerUserDetailsService customerUserDetailsService;

    @Resource
    private LoginFailHandler loginFailHandler;

    // 不需要进行token校验的地址
    private static final List<String> EXCLUDED_URLS = Arrays.asList(
            "/user/login", // 登录接口
            "/user/getImage"  // 公共接口
    );

    // 需要进行图片校验的地址
    private static final List<String> IMAGE_URL = Arrays.asList(
            "/user/login"  // 公共接口
    );

    /**
     * 处理过滤逻辑，用于验证请求的令牌。
     * 如果请求的URL不是登录页面的URL，则进行令牌验证。
     * 如果验证失败，则调用登录失败处理器。
     *
     * @param request     HttpServletRequest对象，代表客户端的HTTP请求
     * @param response    HttpServletResponse对象，用于向客户端发送HTTP响应
     * @param filterChain FilterChain对象，用于继续或终止过滤器链中的下一个过滤器
     * @throws ServletException 如果处理请求时发生Servlet相关异常
     * @throws IOException      如果处理请求时发生IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String url = request.getRequestURI();
        // 当请求的URL不是登录页面的URL时，进行令牌验证
        if (!EXCLUDED_URLS.contains(url)) {
            try {
                validateToken(request, response); // 验证令牌
            } catch (AuthenticationException e) {
                // 令牌验证失败，调用登录失败处理器处理异常
                loginFailHandler.onAuthenticationFailure(request, response, e);
            }
        }
        // 当请求的URL是登录页面的URL时，进行图片验证码验证
        if (IMAGE_URL.contains(url)) {
            try {
                validateImage(request, response); // 验证图片
            } catch (Exception e) {
                response.setContentType("application/json;charset=utf-8");
                ServletOutputStream outputStream = response.getOutputStream();
                String result = JSONUtil.toJsonStr(new ResultUtil().fail(200, "图片校验失败"));
                outputStream.write(result.getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
                outputStream.close();
            }
        }
        // 继续过滤器链的处理
        doFilter(request, response, filterChain);
    }

    /**
     * @return boolean
     * @description 校验图片验证码
     * @author weijianxin
     * @date 2025/1/7 13:53
     * @param: request
     * @param: response
     */
    private boolean validateImage(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String uuid = request.getHeader("uuid");
        String imgCode = request.getHeader("imgCode");
        if (!redisUtils.containsMapKey(RedisConstant.RPKEY_IMAGE_CODE, SaltConstant.SALT + uuid)) {
            response.setContentType("application/json;charset=utf-8");
            ServletOutputStream outputStream = response.getOutputStream();
            String result = JSONUtil.toJsonStr(new ResultUtil().fail(200, "图片验证码已过期，请重新生成验证码"));
            outputStream.write(result.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
        }
        String code = String.valueOf(redisUtils.getMapCache(RedisConstant.RPKEY_IMAGE_CODE, SaltConstant.SALT + uuid));
        redisUtils.removeMapCache(RedisConstant.RPKEY_IMAGE_CODE, SaltConstant.SALT + uuid);
        if (!imgCode.equals(code)) {
            response.setContentType("application/json;charset=utf-8");
            ServletOutputStream outputStream = response.getOutputStream();
            String result = JSONUtil.toJsonStr(new ResultUtil().fail(200, "图片验证码输入错误，请重试"));
            outputStream.write(result.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
        }
        return false;
    }


    /**
     * 验证令牌（token）的有效性。
     *
     * @param request  HttpServletRequest对象，用于获取请求头或参数中的token。
     * @param response HttpServletResponse对象，用于在验证失败时设置响应状态码。
     * @throws AuthenticationException 如果token验证失败，抛出CustomerAuthenticationException异常。
     */
    private void validateToken(HttpServletRequest request,
                               HttpServletResponse response) throws AuthenticationException {
        // 尝试从请求头获取token，如果不存在则从请求参数中获取
        String token = request.getHeader("token");
        if (StrUtil.isEmpty(token)) {
            token = request.getParameter("token");
        }
        // 如果token为空，则抛出异常
        if (StrUtil.isEmpty(token)) {
            throw new CustomerAuthenticationException("token为空");
        }
        // 从Redis中获取tokenKey对应的值，用于和请求中的token进行比较
        String tokenKey = "token:" + token;
        String tokenValue = (String) redisUtils.getMapCache(RedisConstant.RPKEY_SECURITY_TOKEN, tokenKey);
        // 如果Redis中不存在tokenKey对应的值，则抛出token已过期异常
        if (StrUtil.isEmpty(tokenValue)) {
            throw new CustomerAuthenticationException("token已过期");
        }
        // 如果请求中的token和Redis中的token不一致，则抛出token错误异常
        if (!StrUtil.equals(token, tokenValue)) {
            throw new CustomerAuthenticationException("token错误");
        }
        // 解析token，获取用户名
        String account = JwtUtils.parseToken(token)
                .getClaim("account").toString();
        // 如果无法解析token，则抛出token解析失败异常
        if (StrUtil.isEmpty(account)) {
            throw new CustomerAuthenticationException("token解析失败");
        }
        // 根据用户名加载用户详情
        UserDetails userDetails = customerUserDetailsService
                .loadUserByUsername(account);
        // 如果用户不存在，则抛出异常
        if (userDetails == null) {
            throw new CustomerAuthenticationException("用户不存在");
        }
        // 创建认证信息，并设置到SecurityContextHolder中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource()
                .buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

    }

}
